/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package jmmos.gameserver.model.skills;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javolution.util.FastMap;

import jmmos.Config;
import jmmos.gameserver.GeoData;
import jmmos.gameserver.datatables.ItemTable;
import jmmos.gameserver.datatables.SkillTable;
import jmmos.gameserver.datatables.SkillTreesData;
import jmmos.gameserver.handler.ITargetTypeHandler;
import jmmos.gameserver.handler.TargetHandler;
import jmmos.gameserver.model.ChanceCondition;
import jmmos.gameserver.model.IChanceSkillTrigger;
import jmmos.gameserver.model.MMOExtractableProductItem;
import jmmos.gameserver.model.MMOExtractableSkill;
import jmmos.gameserver.model.MMOObject;
import jmmos.gameserver.model.PcCondOverride;
import jmmos.gameserver.model.StatsSet;
import jmmos.gameserver.model.actor.MMOAttackable;
import jmmos.gameserver.model.actor.MMOCharacter;
import jmmos.gameserver.model.actor.MMOPlayable;
import jmmos.gameserver.model.actor.MMOSummon;
import jmmos.gameserver.model.actor.instance.MMOCubicInstance;
import jmmos.gameserver.model.actor.instance.MMODoorInstance;
import jmmos.gameserver.model.actor.instance.MMOPcInstance;
import jmmos.gameserver.model.actor.instance.MMOSiegeFlagInstance;
import jmmos.gameserver.model.conditions.Condition;
import jmmos.gameserver.model.effects.EffectTemplate;
import jmmos.gameserver.model.effects.MMOEffect;
import jmmos.gameserver.model.entity.TvTEvent;
import jmmos.gameserver.model.holders.ItemHolder;
import jmmos.gameserver.model.items.MMOArmor;
import jmmos.gameserver.model.items.type.MMOArmorType;
import jmmos.gameserver.model.skills.funcs.Func;
import jmmos.gameserver.model.skills.funcs.FuncTemplate;
import jmmos.gameserver.model.skills.targets.MMOTargetType;
import jmmos.gameserver.model.stats.BaseStats;
import jmmos.gameserver.model.stats.Env;
import jmmos.gameserver.model.stats.Formulas;
import jmmos.gameserver.model.stats.Stats;
import jmmos.gameserver.model.zone.ZoneId;
import jmmos.gameserver.network.SystemMessageId;
import jmmos.gameserver.network.serverpackets.SystemMessage;
import jmmos.gameserver.util.Util;

public abstract class MMOSkill implements IChanceSkillTrigger
{
	protected static final Logger _log = Logger.getLogger(MMOSkill.class.getName());
	
	private static final MMOObject[] _emptyTargetList = new MMOObject[0];
	
	public static final int SKILL_LUCKY = 194;
	public static final int SKILL_CREATE_COMMON = 1320;
	public static final int SKILL_CREATE_DWARVEN = 172;
	public static final int SKILL_CRYSTALLIZE = 248;
	public static final int SKILL_DIVINE_INSPIRATION = 1405;
	public static final int SKILL_GUILD_LUCK = 390;
	public static final int SKILL_NPC_RACE = 4416;
	public static final int SKILL_ONYX_BEAST_TRANSFORMATION = 617;
	
	public static final boolean geoEnabled = Config.GEODATA > 0;
	
	// conditional values
	public static final int COND_RUNNING = 0x0001;
	public static final int COND_WALKING = 0x0002;
	public static final int COND_SIT = 0x0004;
	public static final int COND_BEHIND = 0x0008;
	public static final int COND_CRIT = 0x0010;
	public static final int COND_LOWHP = 0x0020;
	public static final int COND_ROBES = 0x0040;
	public static final int COND_CHARGES = 0x0080;
	public static final int COND_SHIELD = 0x0100;
	
	private static final Func[] _emptyFunctionSet = new Func[0];
	private static final MMOEffect[] _emptyEffectSet = new MMOEffect[0];
	
	// these two build the primary key
	private final int _id;
	private final int _level;
	
	/** Identifier for a skill that client can't display */
	private int _displayId;
	
	// not needed, just for easier debug
	private final String _name;
	private final MMOSkillOpType _operateType;
	private final int _magic;
	private final MMOTraitType _traitType;
	private final boolean _staticReuse;
	private final boolean _staticDamage; // Damage dealing skills do static damage based on the power value.
	private final int _mpConsume;
	private final int _mpInitialConsume;
	private final int _hpConsume;
	private final int _cpConsume;
	
	private final int _targetConsume;
	private final int _targetConsumeId;
	
	private final int _itemConsume;
	private final int _itemConsumeId;
	
	private final int _castRange;
	private final int _effectRange;
	
	// Abnormal levels for skills and their canceling, e.g. poison vs negate
	private final int _abnormalLvl; // e.g. poison or bleed lvl 2
	// Note: see also _effectAbnormalLvl
	private final int _negateLvl; // abnormalLvl is negated with negateLvl
	private final int[] _negateId; // cancels the effect of skill ID
	private final MMOSkillType[] _negateStats; // lists the effect types that are canceled
	private final Map<String, Byte> _negateAbnormals; // lists the effect abnormal types with order below the presented that are canceled
	private final int _maxNegatedEffects; // maximum number of effects to negate
	
	private final boolean _stayAfterDeath; // skill should stay after death
	private final boolean _stayOnSubclassChange; // skill should stay on subclass change
	
	// kill by damage over time
	private final boolean _killByDOT;
	
	private final int _refId;
	// all times in milliseconds
	private final int _hitTime;
	private final int[] _hitTimings;
	// private final int _skillInterruptTime;
	private final int _coolTime;
	private final int _reuseHashCode;
	private final int _reuseDelay;
	
	/** Target type of the skill : SELF, PARTY, GUILD, PET... */
	private final MMOTargetType _targetType;
	private final int _feed;
	// base success chance
	private final double _power;
	private final double _pvpPower;
	private final double _pvePower;
	private final int _magicLevel;
	private final int _levelDepend;
	private final boolean _ignoreResists;
	private final int _minChance;
	private final int _maxChance;
	private final int _blowChance;
	
	private final boolean _isNeutral;
	// Effecting area of the skill, in radius.
	// The radius center varies according to the _targetType:
	// "caster" if targetType = AURA/PARTY/GUILD or "target" if targetType = AREA
	private final int _skillRadius;
	
	private final MMOSkillType _skillType;
	private final MMOSkillType _effectType; // additional effect has a type
	private final int _effectAbnormalLvl; // abnormal level for the additional effect type, e.g. poison lvl 1
	private final int _effectId;
	private final int _effectLvl; // normal effect level
	
	private final boolean _nextActionIsAttack;
	
	private final boolean _removedOnAnyActionExceptMove;
	private final boolean _removedOnDamage;
	
	private final byte _element;
	private final int _elementPower;
	
	private final Stats _stat;
	private final BaseStats _saveVs;
	
	private final int _condition;
	private final int _conditionValue;
	private final boolean _overhit;
	private final int _weaponsAllowed;
	private final int _armorsAllowed;
	
	private final int _minPledgeClass;
	private final boolean _isOffensive;
	private final int _chargeConsume;
	private final int _triggeredId;
	private final int _triggeredLevel;
	private final String _chanceType;
	private final int _soulMaxConsume;
	private final int _numSouls;
	private final int _expNeeded;
	private final int _critChance;
	private final boolean _dependOnTargetBuff;
	
	private final int _transformId;
	private final int _transformDuration;
	
	private final int _afterEffectId;
	private final int _afterEffectLvl;
	private final boolean _isHeroSkill; // If true the skill is a Hero Skill
	private final boolean _isGMSkill; // True if skill is GM skill
	
	private final int _baseCritRate; // percent of success for skill critical hit (especially for PDAM & BLOW - they're not affected by rCrit values or buffs). Default loads -1 for all other skills but 0 to PDAM & BLOW
	private final int _lethalEffect1; // percent of success for lethal 1st effect (hit cp to 1 or if mob hp to 50%) (only for PDAM skills)
	private final int _lethalEffect2; // percent of success for lethal 2nd effect (hit cp,hp to 1 or if mob hp to 1) (only for PDAM skills)
	private final boolean _directHpDmg; // If true then dmg is being make directly
	private final boolean _isTriggeredSkill; // If true the skill will take activation buff slot instead of a normal buff slot
	private final float _sSBoost; // If true skill will have SoulShot boost (power*2)
	private final int _aggroPoints;
	
	protected List<Condition> _preCondition;
	protected List<Condition> _itemPreCondition;
	protected FuncTemplate[] _funcTemplates;
	public EffectTemplate[] _effectTemplates;
	protected EffectTemplate[] _effectTemplatesSelf;
	protected EffectTemplate[] _effectTemplatesPassive;
	
	protected ChanceCondition _chanceCondition = null;
	
	// Flying support
	private final String _flyType;
	private final int _flyRadius;
	private final float _flyCourse;
	
	private final boolean _isDebuff;
	
	private final String _attribute;
	
	private final boolean _ignoreShield;
	
	private final boolean _isSuicideAttack;
	private final boolean _canBeReflected;
	private final boolean _canBeDispeled;
	
	private final boolean _isGuildSkill;
	private final boolean _excludedFromCheck;
	private final boolean _simultaneousCast;
	
	private MMOExtractableSkill _extractableItems = null;
	
	private final int _maxTargets;
	private final boolean _isStaticHeal;
	
	private int _npcId = 0;
	
	protected MMOSkill(StatsSet set)
	{
		_id = set.getInteger("skill_id");
		_level = set.getInteger("level");
		_refId = set.getInteger("referenceId", 0);
		_displayId = set.getInteger("displayId", _id);
		_name = set.getString("name");
		_operateType = set.getEnum("operateType", MMOSkillOpType.class);
		_magic = set.getInteger("isMagic", 0);
		_traitType = set.getEnum("trait", MMOTraitType.class, MMOTraitType.NONE);
		_staticReuse = set.getBool("staticReuse", false);
		_staticDamage = set.getBool("staticDamage", false);
		_mpConsume = set.getInteger("mpConsume", 0);
		_mpInitialConsume = set.getInteger("mpInitialConsume", 0);
		_hpConsume = set.getInteger("hpConsume", 0);
		_cpConsume = set.getInteger("cpConsume", 0);
		_targetConsume = set.getInteger("targetConsumeCount", 0);
		_targetConsumeId = set.getInteger("targetConsumeId", 0);
		_itemConsume = set.getInteger("itemConsumeCount", 0);
		_itemConsumeId = set.getInteger("itemConsumeId", 0);
		_afterEffectId = set.getInteger("afterEffectId", 0);
		_afterEffectLvl = set.getInteger("afterEffectLvl", 1);
		
		_castRange = set.getInteger("castRange", -1);
		_effectRange = set.getInteger("effectRange", -1);
		
		_abnormalLvl = set.getInteger("abnormalLvl", -1);
		_effectAbnormalLvl = set.getInteger("effectAbnormalLvl", -1); // support for a separate effect abnormal lvl, e.g. poison inside a different skill
		_negateLvl = set.getInteger("negateLvl", -1);
		
		_attribute = set.getString("attribute", "");
		String str = set.getString("negateStats", "");
		
		if (str.isEmpty())
		{
			_negateStats = new MMOSkillType[0];
		}
		else
		{
			String[] stats = str.split(" ");
			MMOSkillType[] array = new MMOSkillType[stats.length];
			
			for (int i = 0; i < stats.length; i++)
			{
				MMOSkillType type = null;
				try
				{
					type = Enum.valueOf(MMOSkillType.class, stats[i]);
				}
				catch (Exception e)
				{
					throw new IllegalArgumentException("SkillId: " + _id + "Enum value of type " + MMOSkillType.class.getName() + "required, but found: " + stats[i]);
				}
				
				array[i] = type;
			}
			_negateStats = array;
		}
		
		String negateAbnormals = set.getString("negateAbnormals", null);
		if ((negateAbnormals != null) && !negateAbnormals.isEmpty())
		{
			_negateAbnormals = new FastMap<>();
			for (String ngtStack : negateAbnormals.split(";"))
			{
				String[] ngt = ngtStack.split(",");
				if (ngt.length == 1) // Only abnormalType is present, without abnormalLvl
				{
					_negateAbnormals.put(ngt[0], Byte.MAX_VALUE);
				}
				else if (ngt.length == 2) // Both abnormalType and abnormalLvl are present
				{
					try
					{
						_negateAbnormals.put(ngt[0], Byte.parseByte(ngt[1]));
					}
					catch (Exception e)
					{
						throw new IllegalArgumentException("SkillId: " + _id + " Byte value required, but found: " + ngt[1]);
					}
				}
				else
				{
					throw new IllegalArgumentException("SkillId: " + _id + ": Incorrect negate Abnormals for " + ngtStack + ". Lvl: abnormalType1,abnormalLvl1;abnormalType2,abnormalLvl2;abnormalType3,abnormalLvl3... or abnormalType1;abnormalType2;abnormalType3...");
				}
			}
		}
		else
		{
			_negateAbnormals = null;
		}
		
		String negateId = set.getString("negateId", null);
		if (negateId != null)
		{
			String[] valuesSplit = negateId.split(",");
			_negateId = new int[valuesSplit.length];
			for (int i = 0; i < valuesSplit.length; i++)
			{
				_negateId[i] = Integer.parseInt(valuesSplit[i]);
			}
		}
		else
		{
			_negateId = new int[0];
		}
		_maxNegatedEffects = set.getInteger("maxNegated", 0);
		
		_stayAfterDeath = set.getBool("stayAfterDeath", false);
		_stayOnSubclassChange = set.getBool("stayOnSubclassChange", true);
		
		_killByDOT = set.getBool("killByDOT", false);
		_isNeutral = set.getBool("neutral", false);
		_hitTime = set.getInteger("hitTime", 0);
		String hitTimings = set.getString("hitTimings", null);
		if (hitTimings != null)
		{
			try
			{
				String[] valuesSplit = hitTimings.split(",");
				_hitTimings = new int[valuesSplit.length];
				for (int i = 0; i < valuesSplit.length; i++)
				{
					_hitTimings[i] = Integer.parseInt(valuesSplit[i]);
				}
			}
			catch (Exception e)
			{
				throw new IllegalArgumentException("SkillId: " + _id + " invalid hitTimings value: " + hitTimings + ", \"percent,percent,...percent\" required");
			}
		}
		else
		{
			_hitTimings = new int[0];
		}
		
		_coolTime = set.getInteger("coolTime", 0);
		_isDebuff = set.getBool("isDebuff", false);
		_feed = set.getInteger("feed", 0);
		_reuseHashCode = SkillTable.getSkillHashCode(_id, _level);
		
		if (Config.ENABLE_MODIFY_SKILL_REUSE && Config.SKILL_REUSE_LIST.containsKey(_id))
		{
			if (Config.DEBUG)
			{
				_log.info("*** Skill " + _name + " (" + _level + ") changed reuse from " + set.getInteger("reuseDelay", 0) + " to " + Config.SKILL_REUSE_LIST.get(_id) + " seconds.");
			}
			_reuseDelay = Config.SKILL_REUSE_LIST.get(_id);
		}
		else
		{
			_reuseDelay = set.getInteger("reuseDelay", 0);
		}
		
		_skillRadius = set.getInteger("skillRadius", 0);
		
		_targetType = set.getEnum("target", MMOTargetType.class);
		_power = set.getFloat("power", 0.f);
		_pvpPower = set.getFloat("pvpPower", (float) getPower());
		_pvePower = set.getFloat("pvePower", (float) getPower());
		_magicLevel = set.getInteger("magicLvl", 0);
		_levelDepend = set.getInteger("lvlDepend", 0);
		_ignoreResists = set.getBool("ignoreResists", false);
		_minChance = set.getInteger("minChance", Config.MIN_ABNORMAL_STATE_SUCCESS_RATE);
		_maxChance = set.getInteger("maxChance", Config.MAX_ABNORMAL_STATE_SUCCESS_RATE);
		_stat = set.getEnum("stat", Stats.class, null);
		_ignoreShield = set.getBool("ignoreShld", false);
		_skillType = set.getEnum("skillType", MMOSkillType.class, MMOSkillType.DUMMY);
		_effectType = set.getEnum("effectType", MMOSkillType.class, null);
		_effectId = set.getInteger("effectId", 0);
		_effectLvl = set.getInteger("effectLevel", 0);
		
		_nextActionIsAttack = set.getBool("nextActionAttack", false);
		
		_removedOnAnyActionExceptMove = set.getBool("removedOnAnyActionExceptMove", false);
		_removedOnDamage = set.getBool("removedOnDamage", _skillType == MMOSkillType.SLEEP);
		
		_element = set.getByte("element", (byte) -1);
		_elementPower = set.getInteger("elementPower", 0);
		
		_saveVs = set.getEnum("saveVs", BaseStats.class, BaseStats.NULL);
		
		_condition = set.getInteger("condition", 0);
		_conditionValue = set.getInteger("conditionValue", 0);
		_overhit = set.getBool("overHit", false);
		_isSuicideAttack = set.getBool("isSuicideAttack", false);
		
		String weaponsAllowedString = set.getString("weaponsAllowed", null);
		if ((weaponsAllowedString != null) && !weaponsAllowedString.trim().isEmpty())
		{
			int mask = 0;
			StringTokenizer st = new StringTokenizer(weaponsAllowedString, ",");
			while (st.hasMoreTokens())
			{
				int old = mask;
				String item = st.nextToken().trim();
				if (ItemTable._weaponTypes.containsKey(item))
				{
					mask |= ItemTable._weaponTypes.get(item).mask();
				}
				
				if (ItemTable._armorTypes.containsKey(item))
				{
					mask |= ItemTable._armorTypes.get(item).mask();
				}
				
				if (old == mask)
				{
					_log.info("[weaponsAllowed] Unknown item type name: " + item);
				}
			}
			_weaponsAllowed = mask;
		}
		else
		{
			_weaponsAllowed = 0;
		}
		
		_armorsAllowed = set.getInteger("armorsAllowed", 0);
		
		_minPledgeClass = set.getInteger("minPledgeClass", 0);
		_isOffensive = set.getBool("offensive", isSkillTypeOffensive());
		_chargeConsume = set.getInteger("chargeConsume", 0);
		_triggeredId = set.getInteger("triggeredId", 0);
		_triggeredLevel = set.getInteger("triggeredLevel", 1);
		_chanceType = set.getString("chanceType", "");
		if (!_chanceType.isEmpty() && !_chanceType.isEmpty())
		{
			_chanceCondition = ChanceCondition.parse(set);
		}
		
		_numSouls = set.getInteger("num_souls", 0);
		_soulMaxConsume = set.getInteger("soulMaxConsumeCount", 0);
		_blowChance = set.getInteger("blowChance", 0);
		_expNeeded = set.getInteger("expNeeded", 0);
		_critChance = set.getInteger("critChance", 0);
		
		_transformId = set.getInteger("transformId", 0);
		_transformDuration = set.getInteger("transformDuration", 0);
		
		_isHeroSkill = SkillTreesData.getInstance().isHeroSkill(_id, _level);
		_isGMSkill = SkillTreesData.getInstance().isGMSkill(_id, _level);
		
		_baseCritRate = set.getInteger("baseCritRate", ((_skillType == MMOSkillType.PDAM) || (_skillType == MMOSkillType.BLOW)) ? 0 : -1);
		_lethalEffect1 = set.getInteger("lethal1", 0);
		_lethalEffect2 = set.getInteger("lethal2", 0);
		
		_directHpDmg = set.getBool("dmgDirectlyToHp", false);
		_isTriggeredSkill = set.getBool("isTriggeredSkill", false);
		_sSBoost = set.getFloat("SSBoost", 0.f);
		_aggroPoints = set.getInteger("aggroPoints", 0);
		
		_flyType = set.getString("flyType", null);
		_flyRadius = set.getInteger("flyRadius", 0);
		_flyCourse = set.getFloat("flyCourse", 0);
		_canBeReflected = set.getBool("canBeReflected", true);
		
		_canBeDispeled = set.getBool("canBeDispeled", true);
		
		_isGuildSkill = set.getBool("isGuildSkill", false);
		_excludedFromCheck = set.getBool("excludedFromCheck", false);
		_dependOnTargetBuff = set.getBool("dependOnTargetBuff", false);
		_simultaneousCast = set.getBool("simultaneousCast", false);
		
		String capsuled_items = set.getString("capsuled_items_skill", null);
		if (capsuled_items != null)
		{
			if (capsuled_items.isEmpty())
			{
				_log.warning("Empty Extractable Item Skill data in Skill Id: " + _id);
			}
			
			_extractableItems = parseExtractableSkill(_id, _level, capsuled_items);
		}
		_maxTargets = set.getInteger("maxTargets", -1);
		_isStaticHeal = set.getBool("isStaticHeal", false);
		_npcId = set.getInteger("npcId", 0);
	}
	
	public abstract void useSkill(MMOCharacter caster, MMOObject[] targets);
	
	public final int getArmorsAllowed()
	{
		return _armorsAllowed;
	}
	
	public final int getConditionValue()
	{
		return _conditionValue;
	}
	
	public final MMOSkillType getSkillType()
	{
		return _skillType;
	}
	
	public final MMOTraitType getTraitType()
	{
		return _traitType;
	}
	
	public final byte getElement()
	{
		return _element;
	}
	
	public final int getElementPower()
	{
		return _elementPower;
	}
	
	/**
	 * Return the target type of the skill : SELF, PARTY, GUILD, PET...
	 * @return
	 */
	public final MMOTargetType getTargetType()
	{
		return _targetType;
	}
	
	public final int getCondition()
	{
		return _condition;
	}
	
	public final boolean isOverhit()
	{
		return _overhit;
	}
	
	public final boolean killByDOT()
	{
		return _killByDOT;
	}
	
	public final boolean isSuicideAttack()
	{
		return _isSuicideAttack;
	}
	
	public final boolean allowOnTransform()
	{
		return isPassive();
	}
	
	/**
	 * Return the power of the skill.
	 * @param activeChar
	 * @param target
	 * @param isPvP
	 * @param isPvE
	 * @return
	 */
	public final double getPower(MMOCharacter activeChar, MMOCharacter target, boolean isPvP, boolean isPvE)
	{
		if (activeChar == null)
		{
			return getPower(isPvP, isPvE);
		}
		
		switch (_skillType)
		{
			case DEATHLINK:
			{
				return getPower(isPvP, isPvE) * (-((activeChar.getCurrentHp() * 2) / activeChar.getMaxHp()) + 2);
			}
			case FATAL:
			{
				return getPower(isPvP, isPvE) * (-((target.getCurrentHp() * 2) / target.getMaxHp()) + 2);
			}
			default:
			{
				return getPower(isPvP, isPvE);
			}
		}
	}
	
	public final double getPower()
	{
		return _power;
	}
	
	public final double getPower(boolean isPvP, boolean isPvE)
	{
		return isPvE ? _pvePower : isPvP ? _pvpPower : _power;
	}
	
	public final MMOSkillType[] getNegateStats()
	{
		return _negateStats;
	}
	
	public final Map<String, Byte> getNegateAbnormals()
	{
		return _negateAbnormals;
	}
	
	public final int getAbnormalLvl()
	{
		return _abnormalLvl;
	}
	
	public final int getNegateLvl()
	{
		return _negateLvl;
	}
	
	public final int[] getNegateId()
	{
		return _negateId;
	}
	
	public final int getMagicLevel()
	{
		return _magicLevel;
	}
	
	public final int getMaxNegatedEffects()
	{
		return _maxNegatedEffects;
	}
	
	public final int getLevelDepend()
	{
		return _levelDepend;
	}
	
	/**
	 * Return true if skill should ignore all resistances
	 * @return
	 */
	public final boolean ignoreResists()
	{
		return _ignoreResists;
	}
	
	/**
	 * Return custom minimum skill/effect chance.
	 * @return
	 */
	public final int getMinChance()
	{
		return _minChance;
	}
	
	/**
	 * Return custom maximum skill/effect chance.
	 * @return
	 */
	public final int getMaxChance()
	{
		return _maxChance;
	}
	
	/**
	 * Return true if skill effects should be removed on any action except movement
	 * @return
	 */
	public final boolean isRemovedOnAnyActionExceptMove()
	{
		return _removedOnAnyActionExceptMove;
	}
	
	/**
	 * Return true if skill effects should be removed on damage
	 * @return
	 */
	public final boolean isRemovedOnDamage()
	{
		return _removedOnDamage;
	}
	
	/**
	 * Return the additional effect Id.
	 * @return
	 */
	public final int getEffectId()
	{
		return _effectId;
	}
	
	/**
	 * Return the additional effect level.
	 * @return
	 */
	public final int getEffectLvl()
	{
		return _effectLvl;
	}
	
	public final int getEffectAbnormalLvl()
	{
		return _effectAbnormalLvl;
	}
	
	/**
	 * Return the additional effect skill type (ex : STUN, PARALYZE,...).
	 * @return
	 */
	public final MMOSkillType getEffectType()
	{
		return _effectType;
	}
	
	/**
	 * Return true if character should attack target after skill
	 * @return
	 */
	public final boolean nextActionIsAttack()
	{
		return _nextActionIsAttack;
	}
	
	/**
	 * TODO: Zoey76, temp fix until skill reworks is done.
	 * @return the calculated buff duration used to display buff icons.
	 */
	public final int getBuffDuration()
	{
		int duration = 0;
		final EffectTemplate firstEffect = hasEffects() ? getEffectTemplates()[0] : null;
		if (firstEffect != null)
		{
			duration = firstEffect.abnormalTime * 1000;
			if (firstEffect.counter > 1)
			{
				duration *= firstEffect.counter;
			}
		}
		return duration;
	}
	
	/**
	 * @return Returns the castRange.
	 */
	public final int getCastRange()
	{
		return _castRange;
	}
	
	/**
	 * @return Returns the cpConsume;
	 */
	public final int getCpConsume()
	{
		return _cpConsume;
	}
	
	/**
	 * @return Returns the effectRange.
	 */
	public final int getEffectRange()
	{
		return _effectRange;
	}
	
	/**
	 * @return Returns the hpConsume.
	 */
	public final int getHpConsume()
	{
		return _hpConsume;
	}
	
	/**
	 * @return Returns the id.
	 */
	public final int getId()
	{
		return _id;
	}
	
	/**
	 * @return Returns the boolean _isDebuff.
	 */
	public final boolean isDebuff()
	{
		return _isDebuff;
	}
	
	public int getDisplayId()
	{
		return _displayId;
	}
	
	public void setDisplayId(int id)
	{
		_displayId = id;
	}
	
	public int getTriggeredId()
	{
		return _triggeredId;
	}
	
	public int getTriggeredLevel()
	{
		return _triggeredLevel;
	}
	
	public boolean triggerAnotherSkill()
	{
		return _triggeredId > 1;
	}
	
	/**
	 * Return the skill type (ex : BLEED, SLEEP, WATER...).
	 * @return
	 */
	public final Stats getStat()
	{
		return _stat;
	}
	
	/**
	 * Return skill saveVs base stat (STR, INT ...).
	 * @return
	 */
	public final BaseStats getSaveVs()
	{
		return _saveVs;
	}
	
	/**
	 * @return Returns the _targetConsumeId.
	 */
	public final int getTargetConsumeId()
	{
		return _targetConsumeId;
	}
	
	/**
	 * @return Returns the targetConsume.
	 */
	public final int getTargetConsume()
	{
		return _targetConsume;
	}
	
	/**
	 * @return Returns the itemConsume.
	 */
	public final int getItemConsume()
	{
		return _itemConsume;
	}
	
	/**
	 * @return Returns the itemConsumeId.
	 */
	public final int getItemConsumeId()
	{
		return _itemConsumeId;
	}
	
	/**
	 * @return Returns the level.
	 */
	public final int getLevel()
	{
		return _level;
	}
	
	/**
	 * @return Returns true to set physical skills.
	 */
	public final boolean isPhysical()
	{
		return _magic == 0;
	}
	
	/**
	 * @return Returns true to set magic skills.
	 */
	public final boolean isMagic()
	{
		return _magic == 1;
	}
	
	/**
	 * @return Returns true to set static skills.
	 */
	public final boolean isStatic()
	{
		return _magic == 2;
	}
	
	/**
	 * @return Returns true to set static reuse.
	 */
	public final boolean isStaticReuse()
	{
		return _staticReuse;
	}
	
	public final boolean isStaticDamage()
	{
		return _staticDamage;
	}
	
	/**
	 * @return Returns the mpConsume.
	 */
	public final int getMpConsume()
	{
		return _mpConsume;
	}
	
	/**
	 * @return Returns the mpInitialConsume.
	 */
	public final int getMpInitialConsume()
	{
		return _mpInitialConsume;
	}
	
	/**
	 * @return Returns the name.
	 */
	public final String getName()
	{
		return _name;
	}
	
	/**
	 * @return Returns the reuseDelay.
	 */
	public final int getReuseDelay()
	{
		return _reuseDelay;
	}
	
	public final int getReuseHashCode()
	{
		return _reuseHashCode;
	}
	
	public final int getHitTime()
	{
		return _hitTime;
	}
	
	public final int getHitCounts()
	{
		return _hitTimings.length;
	}
	
	public final int[] getHitTimings()
	{
		return _hitTimings;
	}
	
	/**
	 * @return Returns the coolTime.
	 */
	public final int getCoolTime()
	{
		return _coolTime;
	}
	
	public final int getSkillRadius()
	{
		return _skillRadius;
	}
	
	public final boolean isActive()
	{
		return (_operateType != null) && _operateType.isActive();
	}
	
	public final boolean isPassive()
	{
		return (_operateType != null) && _operateType.isPassive();
	}
	
	public final boolean isToggle()
	{
		return (_operateType != null) && _operateType.isToggle();
	}
	
	public final boolean isChance()
	{
		return (_chanceCondition != null) && isPassive();
	}
	
	public final boolean isTriggeredSkill()
	{
		return _isTriggeredSkill;
	}
	
	public final boolean isDance()
	{
		return _magic == 3;
	}
	
	public final float getSSBoost()
	{
		return _sSBoost;
	}
	
	public final int getAggroPoints()
	{
		return _aggroPoints;
	}
	
	public final boolean useSoulShot()
	{
		switch (getSkillType())
		{
			case PDAM:
			case CHARGEDAM:
			case BLOW:
				return true;
			default:
				return false;
		}
	}
	
	public final boolean useSpiritShot()
	{
		return _magic == 1;
	}
	
	public final boolean useFishShot()
	{
		return ((getSkillType() == MMOSkillType.PUMPING) || (getSkillType() == MMOSkillType.REELING));
	}
	
	public final int getWeaponsAllowed()
	{
		return _weaponsAllowed;
	}
	
	public int getMinPledgeClass()
	{
		return _minPledgeClass;
	}
	
	public final boolean isOffensive()
	{
		return _isOffensive;
	}
	
	public final boolean isNeutral()
	{
		return _isNeutral;
	}
	
	public final boolean isHeroSkill()
	{
		return _isHeroSkill;
	}
	
	public final boolean isGMSkill()
	{
		return _isGMSkill;
	}
	
	public final int getChargeConsume()
	{
		return _chargeConsume;
	}
	
	public final int getNumSouls()
	{
		return _numSouls;
	}
	
	public final int getMaxSoulConsumeCount()
	{
		return _soulMaxConsume;
	}
	
	public final int getExpNeeded()
	{
		return _expNeeded;
	}
	
	public final int getCritChance()
	{
		return _critChance;
	}
	
	public final int getTransformId()
	{
		return _transformId;
	}
	
	public final int getTransformDuration()
	{
		return _transformDuration;
	}
	
	public final int getBaseCritRate()
	{
		return _baseCritRate;
	}
	
	public final int getLethalChance1()
	{
		return _lethalEffect1;
	}
	
	public final int getLethalChance2()
	{
		return _lethalEffect2;
	}
	
	public final boolean getDmgDirectlyToHP()
	{
		return _directHpDmg;
	}
	
	public final String getFlyType()
	{
		return _flyType;
	}
	
	public final int getFlyRadius()
	{
		return _flyRadius;
	}
	
	public final float getFlyCourse()
	{
		return _flyCourse;
	}
	
	public final boolean isPvpSkill()
	{
		switch (_skillType)
		{
			case DOT:
			case BLEED:
			case CONFUSION:
			case POISON:
			case DEBUFF:
			case AGGDEBUFF:
			case STUN:
			case ROOT:
			case FEAR:
			case SLEEP:
			case MDOT:
			case MUTE:
			case PARALYZE:
			case CANCEL:
			case BETRAY:
			case DISARM:
			case AGGDAMAGE:
			case STEAL_BUFF:
			case AGGREDUCE_CHAR:
			case MANADAM:
				return true;
			default:
				return false;
		}
	}
	
	public final boolean isSkillTypeOffensive()
	{
		switch (_skillType)
		{
			case PDAM:
			case MDAM:
			case CPDAM:
			case DOT:
			case CPDAMPERCENT:
			case BLEED:
			case POISON:
			case AGGDAMAGE:
			case DEBUFF:
			case AGGDEBUFF:
			case STUN:
			case ROOT:
			case CONFUSION:
			case ERASE:
			case BLOW:
			case FATAL:
			case FEAR:
			case DRAIN:
			case SLEEP:
			case CHARGEDAM:
			case CONFUSE_MOB_ONLY:
			case DEATHLINK:
			case DETECT_WEAKNESS:
			case MANADAM:
			case MDOT:
			case MUTE:
			case SOULSHOT:
			case SPIRITSHOT:
			case SPOIL:
			case SWEEP:
			case PARALYZE:
			case DRAIN_SOUL:
			case AGGREDUCE:
			case CANCEL:
			case AGGREMOVE:
			case AGGREDUCE_CHAR:
			case BETRAY:
			case DELUXE_KEY_UNLOCK:
			case SOW:
			case DISARM:
			case STEAL_BUFF:
			case INSTANT_JUMP:
				return true;
			default:
				return false;
		}
	}
	
	public final boolean is7Signs()
	{
		if ((_id > 4360) && (_id < 4367))
		{
			return true;
		}
		return false;
	}
	
	public final boolean isStayAfterDeath()
	{
		return _stayAfterDeath;
	}
	
	public final boolean isStayOnSubclassChange()
	{
		return _stayOnSubclassChange;
	}
	
	// int weapons[] = {MMOWeapon.WEAPON_TYPE_ETC, MMOWeapon.WEAPON_TYPE_BOW,
	// MMOWeapon.WEAPON_TYPE_POLE, MMOWeapon.WEAPON_TYPE_DUALFIST,
	// MMOWeapon.WEAPON_TYPE_DUAL, MMOWeapon.WEAPON_TYPE_BLUNT,
	// MMOWeapon.WEAPON_TYPE_SWORD, MMOWeapon.WEAPON_TYPE_DAGGER};
	
	public final boolean getWeaponDependancy(MMOCharacter activeChar)
	{
		if (getWeaponDependancy(activeChar, false))
		{
			return true;
		}
		
		final SystemMessage message = SystemMessage.getSystemMessage(SystemMessageId.S1_CANNOT_BE_USED);
		message.addSkillName(this);
		activeChar.sendPacket(message);
		return false;
	}
	
	public final boolean getWeaponDependancy(MMOCharacter activeChar, boolean chance)
	{
		int weaponsAllowed = getWeaponsAllowed();
		// check to see if skill has a weapon dependency.
		if (weaponsAllowed == 0)
		{
			return true;
		}
		
		int mask = 0;
		
		if (activeChar.getActiveWeaponItem() != null)
		{
			mask |= activeChar.getActiveWeaponItem().getItemType().mask();
		}
		if ((activeChar.getSecondaryWeaponItem() != null) && (activeChar.getSecondaryWeaponItem() instanceof MMOArmor))
		{
			mask |= ((MMOArmorType) activeChar.getSecondaryWeaponItem().getItemType()).mask();
		}
		
		if ((mask & weaponsAllowed) != 0)
		{
			return true;
		}
		
		return false;
	}
	
	public boolean checkCondition(MMOCharacter activeChar, MMOObject target, boolean itemOrWeapon)
	{
		if (activeChar.canOverrideCond(PcCondOverride.SKILL_CONDITIONS) && !Config.GM_SKILL_RESTRICTION)
		{
			return true;
		}
		if ((getCondition() & MMOSkill.COND_SHIELD) != 0)
		{
			// MMOItemInstance dummy = activeChar.getInventory().getPaperdollItem(Inventory.PAPERDOLL_RHAND);
			// MMOArmor armorPiece = (MMOArmor) dummy.getItem();
			// TODO add checks for shield here.
		}
		
		List<Condition> preCondition = _preCondition;
		if (itemOrWeapon)
		{
			preCondition = _itemPreCondition;
		}
		
		if ((preCondition == null) || preCondition.isEmpty())
		{
			return true;
		}
		
		for (Condition cond : preCondition)
		{
			Env env = new Env();
			env.setCharacter(activeChar);
			if (target instanceof MMOCharacter)
			{
				env.setTarget((MMOCharacter) target);
			}
			env.setSkill(this);
			
			if (!cond.test(env))
			{
				String msg = cond.getMessage();
				int msgId = cond.getMessageId();
				if (msgId != 0)
				{
					SystemMessage sm = SystemMessage.getSystemMessage(msgId);
					if (cond.isAddName())
					{
						sm.addSkillName(_id);
					}
					activeChar.sendPacket(sm);
				}
				else if (msg != null)
				{
					activeChar.sendMessage(msg);
				}
				return false;
			}
		}
		return true;
	}
	
	public final MMOObject[] getTargetList(MMOCharacter activeChar, boolean onlyFirst)
	{
		// Init to null the target of the skill
		MMOCharacter target = null;
		
		// Get the MMOObjcet targeted by the user of the skill at this moment
		MMOObject objTarget = activeChar.getTarget();
		// If the MMOObject targeted is a MMOCharacter, it becomes the MMOCharacter target
		if (objTarget instanceof MMOCharacter)
		{
			target = (MMOCharacter) objTarget;
		}
		
		return getTargetList(activeChar, onlyFirst, target);
	}
	
	/**
	 * Return all targets of the skill in a table in function a the skill type.<br>
	 * <B><U>Values of skill type</U>:</B>
	 * <ul>
	 * <li>ONE : The skill can only be used on the MMOPcInstance targeted, or on the caster if it's a MMOPcInstance and no MMOPcInstance targeted</li>
	 * <li>SELF</li>
	 * <li>HOLY, UNDEAD</li>
	 * <li>PET</li>
	 * <li>AURA, AURA_CLOSE</li>
	 * <li>AREA</li>
	 * <li>MULTIFACE</li>
	 * <li>PARTY, GUILD</li>
	 * <li>CORPSE_PLAYER, CORPSE_MOB, CORPSE_GUILD</li>
	 * <li>UNLOCKABLE</li>
	 * <li>ITEM</li>
	 * <ul>
	 * @param activeChar The MMOCharacter who use the skill
	 * @param onlyFirst
	 * @param target
	 * @return
	 */
	public final MMOObject[] getTargetList(MMOCharacter activeChar, boolean onlyFirst, MMOCharacter target)
	{
		final ITargetTypeHandler handler = TargetHandler.getInstance().getHandler(getTargetType());
		if (handler != null)
		{
			try
			{
				return handler.getTargetList(this, activeChar, onlyFirst, target);
			}
			catch (Exception e)
			{
				_log.log(Level.WARNING, "Exception in MMOSkill.getTargetList(): " + e.getMessage(), e);
			}
		}
		activeChar.sendMessage("Target type of skill is not currently handled");
		return _emptyTargetList;
	}
	
	public final MMOObject[] getTargetList(MMOCharacter activeChar)
	{
		return getTargetList(activeChar, false);
	}
	
	public final MMOObject getFirstOfTargetList(MMOCharacter activeChar)
	{
		MMOObject[] targets;
		
		targets = getTargetList(activeChar, true);
		
		if (targets.length == 0)
		{
			return null;
		}
		return targets[0];
	}
	
	/**
	 * Check if should be target added to the target list false if target is dead, target same as caster,<br>
	 * target inside peace zone, target in the same party with caster, caster can see target Additional checks if not in PvP zones (arena, siege):<br>
	 * target in not the same guild and alliance with caster, and usual skill PvP check. If TvT event is active - performing additional checks. Caution: distance is not checked.
	 * @param caster
	 * @param target
	 * @param skill
	 * @param sourceInArena
	 * @return
	 */
	public static final boolean checkForAreaOffensiveSkills(MMOCharacter caster, MMOCharacter target, MMOSkill skill, boolean sourceInArena)
	{
		if ((target == null) || target.isDead() || (target == caster))
		{
			return false;
		}
		
		final MMOPcInstance player = caster.getActingPlayer();
		final MMOPcInstance targetPlayer = target.getActingPlayer();
		if (player != null)
		{
			if (targetPlayer != null)
			{
				if ((targetPlayer == caster) || (targetPlayer == player))
				{
					return false;
				}
				
				if (targetPlayer.inObserverMode())
				{
					return false;
				}
				
				if (skill.isOffensive() && (player.getSiegeState() > 0) && player.isInsideZone(ZoneId.SIEGE) && (player.getSiegeState() == targetPlayer.getSiegeState()) && (player.getSiegeSide() == targetPlayer.getSiegeSide()))
				{
					return false;
				}
				
				if (skill.isOffensive() && target.isInsideZone(ZoneId.PEACE))
				{
					return false;
				}
				
				if (player.isInParty() && targetPlayer.isInParty())
				{
					// Same party
					if (player.getParty().getLeaderObjectId() == targetPlayer.getParty().getLeaderObjectId())
					{
						return false;
					}
					
					// Same commandchannel
					if ((player.getParty().getCommandChannel() != null) && (player.getParty().getCommandChannel() == targetPlayer.getParty().getCommandChannel()))
					{
						return false;
					}
				}
				
				if (!TvTEvent.checkForTvTSkill(player, targetPlayer, skill))
				{
					return false;
				}
				
				if (!sourceInArena && !(targetPlayer.isInsideZone(ZoneId.PVP) && !targetPlayer.isInsideZone(ZoneId.SIEGE)))
				{
					if ((player.getAllyId() != 0) && (player.getAllyId() == targetPlayer.getAllyId()))
					{
						return false;
					}
					
					if ((player.getGuildId() != 0) && (player.getGuildId() == targetPlayer.getGuildId()))
					{
						return false;
					}
					
					if (!player.checkPvpSkill(targetPlayer, skill, (caster instanceof MMOSummon)))
					{
						return false;
					}
				}
			}
		}
		else
		{
			// target is mob
			if ((targetPlayer == null) && (target instanceof MMOAttackable) && (caster instanceof MMOAttackable))
			{
				String casterEnemyGuild = ((MMOAttackable) caster).getEnemyGuild();
				if ((casterEnemyGuild == null) || casterEnemyGuild.isEmpty())
				{
					return false;
				}
				
				String targetGuild = ((MMOAttackable) target).getGuild();
				if ((targetGuild == null) || targetGuild.isEmpty())
				{
					return false;
				}
				
				if (!casterEnemyGuild.equals(targetGuild))
				{
					return false;
				}
			}
		}
		
		if (geoEnabled && !GeoData.getInstance().canSeeTarget(caster, target))
		{
			return false;
		}
		
		return true;
	}
	
	public static final boolean addSummon(MMOCharacter caster, MMOPcInstance owner, int radius, boolean isDead)
	{
		if (!owner.hasSummon())
		{
			return false;
		}
		return addCharacter(caster, owner.getSummon(), radius, isDead);
	}
	
	public static final boolean addCharacter(MMOCharacter caster, MMOCharacter target, int radius, boolean isDead)
	{
		if (isDead != target.isDead())
		{
			return false;
		}
		
		if ((radius > 0) && !Util.checkIfInRange(radius, caster, target, true))
		{
			return false;
		}
		
		return true;
		
	}
	
	public final Func[] getStatFuncs(MMOEffect effect, MMOCharacter player)
	{
		if (_funcTemplates == null)
		{
			return _emptyFunctionSet;
		}
		
		if (!(player instanceof MMOPlayable) && !(player instanceof MMOAttackable))
		{
			return _emptyFunctionSet;
		}
		
		ArrayList<Func> funcs = new ArrayList<>(_funcTemplates.length);
		
		Env env = new Env();
		env.setCharacter(player);
		env.setSkill(this);
		
		Func f;
		
		for (FuncTemplate t : _funcTemplates)
		{
			
			f = t.getFunc(env, this); // skill is owner
			if (f != null)
			{
				funcs.add(f);
			}
		}
		if (funcs.isEmpty())
		{
			return _emptyFunctionSet;
		}
		
		return funcs.toArray(new Func[funcs.size()]);
	}
	
	public boolean hasEffects()
	{
		return ((_effectTemplates != null) && (_effectTemplates.length > 0));
	}
	
	public EffectTemplate[] getEffectTemplates()
	{
		return _effectTemplates;
	}
	
	public EffectTemplate[] getEffectTemplatesPassive()
	{
		return _effectTemplatesPassive;
	}
	
	public boolean hasSelfEffects()
	{
		return ((_effectTemplatesSelf != null) && (_effectTemplatesSelf.length > 0));
	}
	
	public boolean hasPassiveEffects()
	{
		return ((_effectTemplatesPassive != null) && (_effectTemplatesPassive.length > 0));
	}
	
	/**
	 * Env is used to pass parameters for secondary effects (shield and ss/bss/bsss)
	 * @param effector
	 * @param effected
	 * @param env
	 * @return an array with the effects that have been added to effector
	 */
	public final MMOEffect[] getEffects(MMOCharacter effector, MMOCharacter effected, Env env)
	{
		if (!hasEffects() || isPassive())
		{
			return _emptyEffectSet;
		}
		
		// doors and siege flags cannot receive any effects
		if ((effected instanceof MMODoorInstance) || (effected instanceof MMOSiegeFlagInstance))
		{
			return _emptyEffectSet;
		}
		
		if (effector != effected)
		{
			if (isOffensive() || isDebuff())
			{
				if (effected.isInvul())
				{
					return _emptyEffectSet;
				}
				
				if ((effector instanceof MMOPcInstance) && ((MMOPcInstance) effector).isGM())
				{
					if (!((MMOPcInstance) effector).getAccessLevel().canGiveDamage())
					{
						return _emptyEffectSet;
					}
				}
			}
		}
		
		ArrayList<MMOEffect> effects = new ArrayList<>(_effectTemplates.length);
		
		if (env == null)
		{
			env = new Env();
		}
		
		env.setSkillMastery(Formulas.calcSkillMastery(effector, this));
		env.setCharacter(effector);
		env.setTarget(effected);
		env.setSkill(this);
		
		for (EffectTemplate et : _effectTemplates)
		{
			boolean success = true;
			
			if (et.effectPower > -1)
			{
				success = Formulas.calcEffectSuccess(effector, effected, et, this, env.getShield(), env.isSoulShot(), env.isSpiritShot(), env.isBlessedSpiritShot());
			}
			
			if (success)
			{
				MMOEffect e = et.getEffect(env);
				if (e != null)
				{
					e.scheduleEffect();
					effects.add(e);
				}
			}
			// display fail message only for effects with icons
			else if (et.icon && (effector instanceof MMOPcInstance))
			{
				SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_RESISTED_YOUR_S2);
				sm.addCharName(effected);
				sm.addSkillName(this);
				((MMOPcInstance) effector).sendPacket(sm);
			}
		}
		
		if (effects.isEmpty())
		{
			return _emptyEffectSet;
		}
		
		return effects.toArray(new MMOEffect[effects.size()]);
	}
	
	/**
	 * Warning: this method doesn't consider modifier (shield, ss, sps, bss) for secondary effects
	 * @param effector
	 * @param effected
	 * @return
	 */
	public final MMOEffect[] getEffects(MMOCharacter effector, MMOCharacter effected)
	{
		return getEffects(effector, effected, null);
	}
	
	/**
	 * This method has suffered some changes in CT2.2 ->CT2.3<br>
	 * Effect engine is now supporting secondary effects with independent success/fail calculus from effect skill.<br>
	 * Env parameter has been added to pass parameters like soulshot, spiritshots, blessed spiritshots or shield defense.<br>
	 * Some other optimizations have been done<br>
	 * This new feature works following next rules:
	 * <ul>
	 * <li>To enable feature, effectPower must be over -1 (check DocumentSkill#attachEffect for further information)</li>
	 * <li>If main skill fails, secondary effect always fail</li>
	 * </ul>
	 * @param effector
	 * @param effected
	 * @param env
	 * @return
	 */
	public final MMOEffect[] getEffects(MMOCubicInstance effector, MMOCharacter effected, Env env)
	{
		if (!hasEffects() || isPassive())
		{
			return _emptyEffectSet;
		}
		
		if (effector.getOwner() != effected)
		{
			if (isDebuff() || isOffensive())
			{
				if (effected.isInvul())
				{
					return _emptyEffectSet;
				}
				
				if (effector.getOwner().isGM() && !effector.getOwner().getAccessLevel().canGiveDamage())
				{
					return _emptyEffectSet;
				}
			}
		}
		
		ArrayList<MMOEffect> effects = new ArrayList<>(_effectTemplates.length);
		
		if (env == null)
		{
			env = new Env();
		}
		
		env.setCharacter(effector.getOwner());
		env.setCubic(effector);
		env.setTarget(effected);
		env.setSkill(this);
		
		for (EffectTemplate et : _effectTemplates)
		{
			boolean success = true;
			if (et.effectPower > -1)
			{
				success = Formulas.calcEffectSuccess(effector.getOwner(), effected, et, this, env.getShield(), env.isSoulShot(), env.isSpiritShot(), env.isBlessedSpiritShot());
			}
			
			if (success)
			{
				MMOEffect e = et.getEffect(env);
				if (e != null)
				{
					e.scheduleEffect();
					effects.add(e);
				}
			}
		}
		
		if (effects.isEmpty())
		{
			return _emptyEffectSet;
		}
		
		return effects.toArray(new MMOEffect[effects.size()]);
	}
	
	public final MMOEffect[] getEffectsSelf(MMOCharacter effector)
	{
		if (!hasSelfEffects() || isPassive())
		{
			return _emptyEffectSet;
		}
		
		List<MMOEffect> effects = new ArrayList<>(_effectTemplatesSelf.length);
		
		for (EffectTemplate et : _effectTemplatesSelf)
		{
			Env env = new Env();
			env.setCharacter(effector);
			env.setTarget(effector);
			env.setSkill(this);
			MMOEffect e = et.getEffect(env);
			if (e != null)
			{
				e.setSelfEffect();
				e.scheduleEffect();
				effects.add(e);
			}
		}
		if (effects.isEmpty())
		{
			return _emptyEffectSet;
		}
		
		return effects.toArray(new MMOEffect[effects.size()]);
	}
	
	public final MMOEffect[] getEffectsPassive(MMOCharacter effector)
	{
		if (!hasPassiveEffects())
		{
			return _emptyEffectSet;
		}
		
		List<MMOEffect> effects = new ArrayList<>(_effectTemplatesPassive.length);
		
		for (EffectTemplate et : _effectTemplatesPassive)
		{
			Env env = new Env();
			env.setCharacter(effector);
			env.setTarget(effector);
			env.setSkill(this);
			MMOEffect e = et.getEffect(env);
			if (e != null)
			{
				e.setPassiveEffect();
				e.scheduleEffect();
				effects.add(e);
			}
		}
		if (effects.isEmpty())
		{
			return _emptyEffectSet;
		}
		
		return effects.toArray(new MMOEffect[effects.size()]);
	}
	
	public final void attach(FuncTemplate f)
	{
		if (_funcTemplates == null)
		{
			_funcTemplates = new FuncTemplate[]
			{
				f
			};
		}
		else
		{
			int len = _funcTemplates.length;
			FuncTemplate[] tmp = new FuncTemplate[len + 1];
			System.arraycopy(_funcTemplates, 0, tmp, 0, len);
			tmp[len] = f;
			_funcTemplates = tmp;
		}
	}
	
	public final void attach(EffectTemplate effect)
	{
		if (_effectTemplates == null)
		{
			_effectTemplates = new EffectTemplate[]
			{
				effect
			};
		}
		else
		{
			int len = _effectTemplates.length;
			EffectTemplate[] tmp = new EffectTemplate[len + 1];
			System.arraycopy(_effectTemplates, 0, tmp, 0, len);
			tmp[len] = effect;
			_effectTemplates = tmp;
		}
		
	}
	
	public final void attachSelf(EffectTemplate effect)
	{
		if (_effectTemplatesSelf == null)
		{
			_effectTemplatesSelf = new EffectTemplate[]
			{
				effect
			};
		}
		else
		{
			int len = _effectTemplatesSelf.length;
			EffectTemplate[] tmp = new EffectTemplate[len + 1];
			System.arraycopy(_effectTemplatesSelf, 0, tmp, 0, len);
			tmp[len] = effect;
			_effectTemplatesSelf = tmp;
		}
	}
	
	public final void attachPassive(EffectTemplate effect)
	{
		if (_effectTemplatesPassive == null)
		{
			_effectTemplatesPassive = new EffectTemplate[]
			{
				effect
			};
		}
		else
		{
			int len = _effectTemplatesPassive.length;
			EffectTemplate[] tmp = new EffectTemplate[len + 1];
			System.arraycopy(_effectTemplatesPassive, 0, tmp, 0, len);
			tmp[len] = effect;
			_effectTemplatesPassive = tmp;
		}
	}
	
	public final void attach(Condition c, boolean itemOrWeapon)
	{
		if (itemOrWeapon)
		{
			if (_itemPreCondition == null)
			{
				_itemPreCondition = new ArrayList<>();
			}
			_itemPreCondition.add(c);
		}
		else
		{
			if (_preCondition == null)
			{
				_preCondition = new ArrayList<>();
			}
			_preCondition.add(c);
		}
	}
	
	@Override
	public String toString()
	{
		return "" + _name + "[id=" + _id + ",lvl=" + _level + "]";
	}
	
	/**
	 * @return pet food
	 */
	public int getFeed()
	{
		return _feed;
	}
	
	/**
	 * used for tracking item id in case that item consume cannot be used
	 * @return reference item id
	 */
	public int getReferenceItemId()
	{
		return _refId;
	}
	
	public int getAfterEffectId()
	{
		return _afterEffectId;
	}
	
	public int getAfterEffectLvl()
	{
		return _afterEffectLvl;
	}
	
	@Override
	public boolean triggersChanceSkill()
	{
		return (_triggeredId > 0) && isChance();
	}
	
	@Override
	public int getTriggeredChanceId()
	{
		return _triggeredId;
	}
	
	@Override
	public int getTriggeredChanceLevel()
	{
		return _triggeredLevel;
	}
	
	@Override
	public ChanceCondition getTriggeredChanceCondition()
	{
		return _chanceCondition;
	}
	
	public String getAttributeName()
	{
		return _attribute;
	}
	
	/**
	 * @return the _blowChance
	 */
	public int getBlowChance()
	{
		return _blowChance;
	}
	
	public boolean ignoreShield()
	{
		return _ignoreShield;
	}
	
	public boolean canBeReflected()
	{
		return _canBeReflected;
	}
	
	public boolean canBeDispeled()
	{
		return _canBeDispeled;
	}
	
	public boolean isGuildSkill()
	{
		return _isGuildSkill;
	}
	
	public boolean isExcludedFromCheck()
	{
		return _excludedFromCheck;
	}
	
	public boolean getDependOnTargetBuff()
	{
		return _dependOnTargetBuff;
	}
	
	public boolean isSimultaneousCast()
	{
		return _simultaneousCast;
	}
	
	/**
	 * @param skillId
	 * @param skillLvl
	 * @param values
	 * @return MMOExtractableSkill
	 * @author Zoey76
	 */
	private MMOExtractableSkill parseExtractableSkill(int skillId, int skillLvl, String values)
	{
		final String[] prodLists = values.split(";");
		final List<MMOExtractableProductItem> products = new ArrayList<>();
		String[] prodData;
		for (String prodList : prodLists)
		{
			prodData = prodList.split(",");
			if (prodData.length < 3)
			{
				_log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " -> wrong seperator!");
			}
			List<ItemHolder> items = null;
			double chance = 0;
			int prodId = 0;
			int quantity = 0;
			final int lenght = prodData.length - 1;
			try
			{
				items = new ArrayList<>(lenght / 2);
				for (int j = 0; j < lenght; j++)
				{
					prodId = Integer.parseInt(prodData[j]);
					quantity = Integer.parseInt(prodData[j += 1]);
					if ((prodId <= 0) || (quantity <= 0))
					{
						_log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " wrong production Id: " + prodId + " or wrond quantity: " + quantity + "!");
					}
					items.add(new ItemHolder(prodId, quantity));
				}
				chance = Double.parseDouble(prodData[lenght]);
			}
			catch (Exception e)
			{
				_log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " -> incomplete/invalid production data or wrong seperator!");
			}
			products.add(new MMOExtractableProductItem(items, chance));
		}
		
		if (products.isEmpty())
		{
			_log.warning("Extractable skills data: Error in Skill Id: " + skillId + " Level: " + skillLvl + " -> There are no production items!");
		}
		return new MMOExtractableSkill(SkillTable.getSkillHashCode(skillId, skillLvl), products);
	}
	
	public MMOExtractableSkill getExtractableSkill()
	{
		return _extractableItems;
	}
	
	public int getMaxTargets()
	{
		return _maxTargets;
	}
	
	public boolean isStaticHeal()
	{
		return _isStaticHeal;
	}
	
	/**
	 * @return the _npcId
	 */
	public int getNpcId()
	{
		return _npcId;
	}
}